/**
 * @file   PacketHeader.cpp
 * @author Djuro Drljaca (djurodrljaca@gmail.com)
 * @date   2014-04-20
 * @brief  DON packet header object.
 *
 * Copyright 2014  Djuro Drljaca (djurodrljaca@gmail.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <DON/Core/Packet/PacketHeader.h>
#include <DON/Core/Crc16/Crc16.h>
#include <DON/Core/DataType/UInt16.h>
#include <QtCore/QByteArray>

using namespace DON::Core::Packet;

class PacketHeader::Private
{
public:
    Private()
        : source(0u),
          destination(0u),
          packetType(PacketType_Invalid),
          sequence(0u),
          payloadLength(0u),
          payloadChecksum(0u)
    { }

    quint16 source;
    quint16 destination;
    PacketType packetType;
    quint16 sequence;
    quint16 payloadLength;
    quint16 payloadChecksum;
};

PacketHeader::PacketHeader()
    : d(new Private())
{
}

PacketHeader::PacketHeader(const quint16 source,
                           const quint16 destination,
                           const PacketType packetType,
                           const quint16 sequence,
                           const quint16 payloadLength,
                           const quint16 payloadChecksum)
    : d(new Private())
{
    d->source = source;
    d->destination = destination;
    d->packetType = packetType;
    d->sequence = sequence;
    d->payloadLength = payloadLength;
    d->payloadChecksum = payloadChecksum;
}

PacketHeader::PacketHeader(const PacketHeader &other)
    : d(new Private())
{
    d->source = other.d->source;
    d->destination = other.d->destination;
    d->packetType = other.d->packetType;
    d->sequence = other.d->sequence;
    d->payloadLength = other.d->payloadLength;
    d->payloadChecksum = other.d->payloadChecksum;
}

PacketHeader::~PacketHeader()
{
    delete d;
}

PacketHeader & PacketHeader::operator=(const PacketHeader &other)
{
    d->source = other.d->source;
    d->destination = other.d->destination;
    d->packetType = other.d->packetType;
    d->sequence = other.d->sequence;
    d->payloadLength = other.d->payloadLength;
    d->payloadChecksum = other.d->payloadChecksum;

    return *this;
}

quint16 PacketHeader::getSource() const
{
    return d->source;
}

quint16 PacketHeader::getDestination() const
{
    return d->destination;
}

PacketType PacketHeader::getPacketType() const
{
    return d->packetType;
}

quint16 PacketHeader::getSequence() const
{
    return d->sequence;
}

quint16 PacketHeader::getPayloadLength() const
{
    return d->payloadLength;
}

quint16 PacketHeader::getPayloadChecksum() const
{
    return d->payloadChecksum;
}

bool PacketHeader::fromBinary(const QByteArray &data, const int startIndex)
{
    bool success = false;

    if ((data.size() > 0) &&
        (startIndex >= 0) &&
        ((startIndex + 13) <= data.size()))
    {
        using namespace DON::Core::DataType;

        // Source
        const quint16 source = UInt16::fromBinary(data, startIndex, &success);

        // Destination
        quint16 destination = 0u;

        if (success)
        {
            destination = UInt16::fromBinary(data, (startIndex + 2), &success);
        }

        // Packet Type
        PacketType packetType = PacketType_Invalid;

        if (success)
        {
            packetType = fromBinaryToPacketType(data.at(startIndex + 4));

            if (packetType == PacketType_Invalid)
            {
                success = false;
            }
        }

        // Sequence
        quint16 sequence = 0u;

        if (success)
        {
            sequence = UInt16::fromBinary(data, (startIndex + 5), &success);
        }

        // Payload Length
        quint16 payloadLength = 0u;

        if (success)
        {
            payloadLength = UInt16::fromBinary(data, (startIndex + 7), &success);
        }

        // Payload Checksum
        quint16 payloadChecksum = 0u;

        if (success)
        {
            payloadChecksum = UInt16::fromBinary(data, (startIndex + 9), &success);
        }

        // Header Checksum
        quint16 headerChecksum = 0u;

        if (success)
        {
            headerChecksum = UInt16::fromBinary(data, (startIndex + 11), &success);
        }

        // Verify header checksum
        quint16 checksum = 0u;

        if (success)
        {
            checksum = DON::Core::Crc16::calculate(data, startIndex, 11, &success);

            if (success)
            {
                if (checksum != headerChecksum)
                {
                    success = false;
                }
            }
        }

        // Save packet header
        if (success)
        {
            d->source = source;
            d->destination = destination;
            d->packetType = packetType;
            d->sequence = sequence;
            d->payloadLength = payloadLength;
            d->payloadChecksum = payloadChecksum;
        }
    }

    return success;
}

QByteArray PacketHeader::toBinary() const
{
    QByteArray data;

    // Check if packet type is valid
    if (isPacketTypeValid(d->packetType))
    {
        using namespace DON::Core::DataType;

        // Source
        data.append(UInt16::toBinary(d->source));

        // Destination
        data.append(UInt16::toBinary(d->destination));

        // Packet Type
        data.append(fromPacketTypeToBinary(d->packetType));

        // Sequence
        data.append(UInt16::toBinary(d->sequence));

        // Payload Length
        data.append(UInt16::toBinary(d->payloadLength));

        // Payload Checksum/ Payload Length
        data.append(UInt16::toBinary(d->payloadChecksum));

        // Header Checksum
        bool success = false;
        const quint16 headerChecksum = DON::Core::Crc16::calculate(data, 0, 11, &success);

        if (success)
        {
            data.append(UInt16::toBinary(headerChecksum));
        }
        else
        {
            // Error, clear data
            data.clear();
        }
    }

    return data;
}
